home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1994 June: Reference Library / Dev.CD Jun 94.toast / Periodicals / develop / develop Issue 18 / develop 18 code / Apple Guide Sample / Source / UApp.cp < prev    next >
Encoding:
Text File  |  1994-04-01  |  33.4 KB  |  1,289 lines  |  [TEXT/MPS ]

  1. // Copyright ©1994 Apple Computer, Inc.
  2. // Author: John Powers
  3. // Date:   13-Mar-94
  4.  
  5. // UApp.cp
  6. // The derived application class.
  7. // The TApplication and TDocument folders
  8. // have been left unchanged  from the developer CD.
  9. // Our use of the application is tailored in the classes below.
  10.  
  11. #ifndef __UAPP__
  12.     #include "UApp.h"
  13. #endif
  14.  
  15. #if __WantMoGuide__
  16.     #ifndef __UAPPMO__
  17.         #include "UAppMo.h"
  18.     #endif
  19. #endif
  20.  
  21.         // Segment
  22.  
  23. #pragma segment Main
  24.  
  25. // =========================================================================
  26. // main
  27. // ---------------------------------------------------------------------
  28. // main
  29. // Application entry point.
  30. // It all starts here.
  31. // If we need it, we can make ourApp global.
  32. int
  33. main()
  34. {
  35. #if __WantMoGuide__
  36.     TAppMo* ourApp = new TAppMo;
  37. #else
  38.     TApp* ourApp = new TApp;
  39. #endif
  40.     if(!ourApp)
  41.     {
  42.         return 0;
  43.     }
  44.             // Do basic initialization, then wait in
  45.             // the event loop for the startup event.
  46.     if(ourApp->Init()==noErr)
  47.     {
  48.         ourApp->EventLoop();
  49.         ourApp->Quit();
  50.     }
  51.     return 0;
  52. }
  53.  
  54. // ------------------------------------------------------------------------
  55. // AlertIfError
  56. // Display an alert if an error code is passed.
  57. // Return the error code.
  58. OSErr
  59. AlertIfError(OSErr err)
  60. {
  61.     if(err!=noErr)
  62.     {
  63.         DialogPtr    pDlog;
  64.         GrafPtr        pOldPort;
  65.         Boolean        isShowing=true;
  66.         GetPort(&pOldPort);
  67.         pDlog = GetNewDialog(kAlertIfErrorDialogID, nil, FRONT_WINDOW);
  68.         if(pDlog)
  69.         {
  70.             Handle    hDItem;
  71.             short    itemType;
  72.             Rect    itemRect;
  73.             Str255    textStr;
  74.                 // Error number
  75.             NumToString(err, textStr);
  76.             GetDItem(pDlog, kAlertIfErrorErrNum, &itemType, &hDItem, &itemRect);
  77.             SetIText(hDItem, textStr);
  78.                 // Show dialog.
  79.             CenterWindow(pDlog);
  80.             SetPort(pDlog);
  81.             ShowWindow(pDlog);
  82.                 // Draw default outline around OK button.
  83.             GetDItem(pDlog, kAlertIfErrorOK, &itemType, &hDItem, &itemRect);
  84.             PenSize(3,3);
  85.             InsetRect(&itemRect, -4, -4);
  86.             FrameRoundRect(&itemRect, 16, 16);
  87.             PenNormal();
  88.                 // Let user read.
  89.             short itemHit;
  90.             while (isShowing) {
  91.                 ModalDialog(nil, &itemHit);
  92.                 isShowing = itemHit!=kAlertIfErrorOK;
  93.                 }
  94.             DisposeDialog(pDlog);
  95.         }
  96.         SetPort(pOldPort);
  97.     }
  98.     return err;
  99. }
  100.  
  101. // ------------------------------------------------------------------------
  102. // CenterWindow
  103. //
  104. void
  105. CenterWindow(WindowPtr pWin)
  106. {
  107.     Point    windowLoc;
  108.     Rect    boundsRect;
  109.     GetWindowBounds(pWin, &boundsRect);
  110.     short windowHeight = pWin->portRect.bottom - pWin->portRect.top;
  111.     short windowWidth = pWin->portRect.right - pWin->portRect.left;
  112.     short boundsHeight = boundsRect.bottom - boundsRect.top;
  113.     short boundsWidth = boundsRect.right - boundsRect.left;
  114.     windowLoc.v = boundsRect.top + ((boundsHeight - windowHeight) / 2);
  115.     windowLoc.h = boundsRect.left + ((boundsWidth - windowWidth) / 2);
  116.     MoveWindow(pWin, windowLoc.h, windowLoc.v, false);
  117. }
  118.  
  119. // ------------------------------------------------------------------------
  120. // GetWindowBounds
  121. // Get the bounds for the window.
  122. // The menubar is excluded from the main device bounds.
  123. // If the window overlaps devices, use the smallest device
  124. // for the window top and bottom bounds.
  125. // 
  126. void
  127. GetWindowBounds(WindowPtr pWin, Rect* pBoundsRect)
  128. {
  129.             // See if we have Color QuickDraw.
  130.     SysEnvRec    sysEnv;
  131.     SysEnvirons( curSysEnvVers, &sysEnv );
  132.     if (!sysEnv.hasColorQD)
  133.         *pBoundsRect = qd.screenBits.bounds;
  134.     else
  135.     {
  136.         typedef union {
  137.             Rect    rect;
  138.             struct {
  139.                 Point    topleft;
  140.                 Point    botright;
  141.             } corner;
  142.         } GlobRect;
  143.         Rect        deviceRect;
  144.         GlobRect    windowRect;
  145.         Rect        overlapRect;
  146.         GDHandle    hGD;
  147.         GDHandle    hGDMain = GetMainDevice();
  148.         GrafPtr        savePort;
  149.                 // Get our window's rectangle in global coordinates.
  150.         windowRect.rect = pWin->portRect;
  151.         GetPort(&savePort);
  152.         SetPort(pWin);
  153.         LocalToGlobal(&windowRect.corner.topleft);
  154.         LocalToGlobal(&windowRect.corner.botright);
  155.                 // Initialize *pBoundsRect.
  156.         *pBoundsRect = (*GetGrayRgn())->rgnBBox;
  157.                 // Loop to examine each device for overlap with our window.
  158.         for (hGD = GetDeviceList(); hGD; hGD = GetNextDevice(hGD))
  159.         {
  160.                     // Look for active screen device.
  161.                 if (TestDeviceAttribute(hGD, screenDevice))
  162.                 {
  163.                     if (TestDeviceAttribute(hGD, screenActive))
  164.                     {
  165.                         deviceRect = (**hGD).gdRect;    // global bounds of device.
  166.                                 // Check for overlap of device and window.
  167.                         if (SectRect(&deviceRect, &windowRect.rect, &overlapRect))
  168.                         {
  169.                                 // We have overlap; if main device, exclude menubar.
  170.                             if(hGD==hGDMain)
  171.                             {
  172.                                 deviceRect.top += GetMBarHeight();
  173.                             } // if(hGD…
  174.                                 // Set top and bottom bounds for window.
  175.                             if(pBoundsRect->top < deviceRect.top)
  176.                                                     pBoundsRect->top = deviceRect.top;
  177.                             if(pBoundsRect->bottom > deviceRect.bottom)
  178.                                                     pBoundsRect->bottom = deviceRect.bottom;
  179.                                                     
  180.                             // clip left and right to left and right side of screens on which
  181.                             // the window resides
  182.                             
  183.                             if ( windowRect.rect.left > deviceRect.left && 
  184.                                     pBoundsRect->left < deviceRect.left )
  185.                                 {
  186.                                 pBoundsRect->left = deviceRect.left;
  187.                                 }
  188.                             if ( windowRect.rect.right < deviceRect.right && 
  189.                                     pBoundsRect->right > deviceRect.right )
  190.                                 {
  191.                                 pBoundsRect->right = deviceRect.right;
  192.                                 }
  193.                                 
  194.                         } // if(SectRect…
  195.                     } // if(TestDeviceAttribute…
  196.                 } // if(TestDeviceAttribute…
  197.         } // for(hGD…
  198.         SetPort(savePort);
  199.     }
  200. };
  201.  
  202. // ------------------------------------------------------------------------
  203. // TApp::HandleAECore
  204. // Handles the core events.
  205. // Comes from the Finder after our application is launched.
  206. // When we are launched, we initialize and then wait for a core event.
  207. // The kAEOpenApplication or kAEOpenDocuments event starts the action!
  208. // The refCon contains our application object.
  209. // Should be in a locked, unpurgeable segment.
  210. //
  211. // Unfortunately, the DTS sample TApplication does not process
  212. // high level events.  We fix that by overriding the EventLoop
  213. // and adding high-level event processing.
  214. //
  215. pascal OSErr
  216. TApp::HandleAECore(AppleEvent& theAppleEvent,
  217.                         AppleEvent& /*theReply*/, long refCon)
  218. {
  219.     OSType        eventId;
  220.     Size        actualSize;
  221.     DescType    returnedType;
  222.     AEKeyword    theAEKeyword;
  223.     OSErr        err=noErr;
  224.     AEDescList    docList;
  225.     long        docCnt;
  226.     FSSpec        fileSpec;
  227.     TApp*        ourApp=(TApp*)refCon;
  228.             // Useless without our application object.
  229.     if(!ourApp)
  230.         return kErrNoAppObject;
  231.             // A core event, get event id.
  232.     err = AEGetAttributePtr(&theAppleEvent, keyEventIDAttr,
  233.                                 typeType, &returnedType,
  234.                                 (Ptr) &eventId, sizeof(eventId),
  235.                                 &actualSize);
  236.     switch (eventId)
  237.     {
  238.         case kAEOpenApplication:
  239.                 // Startup.  Attempt autostart if possible.
  240.             err = ourApp->Start();
  241.             if(err==noErr)
  242.             {
  243.                 if(ourApp->fAutoStart)
  244.                 {
  245.                         // Attempt autostart and get preset database spec.
  246.                     err = ourApp->fAutoStart->AttemptAutoStart();
  247.                     ourApp->fAutoStart->GetFile(ourApp->fPresetGuideFile);
  248.                     if(err!=noErr)
  249.                         ourApp->ExitLoop();
  250.                     else if(gAGuideAvailable)
  251.                     {
  252.                         if(AGGetStatus()==kAGIsActive)
  253.                         {
  254.                                 // Did start Apple Guide with a database.
  255.                                 // Update our database variables.
  256.                             ourApp->fAutoStart->GetRefNum(ourApp->fGuideRefNum);
  257.                         }
  258.                     }
  259.                 }
  260.             }
  261.             break;
  262.         case kAEOpenDocuments:
  263.                 // Ask Apple Guide to open one document.
  264.             err = AEGetParamDesc(&theAppleEvent, keyDirectObject,
  265.                                     typeAEList, &docList);
  266.             if(err==noErr)
  267.             {
  268.                 err = AECountItems(&docList, &docCnt);
  269.                 if(err==noErr && docCnt>0)
  270.                 {
  271.                     err = AEGetNthPtr(&docList, 1, typeFSS, &theAEKeyword,
  272.                                     &returnedType, (Ptr) &fileSpec,
  273.                                     sizeof(fileSpec), &actualSize);
  274.                     if(err==noErr)
  275.                     {
  276.                             err = ourApp->Start();
  277.                             if(err==noErr)
  278.                                 err = ourApp->OpenGuideDatabase(&fileSpec);
  279.                     }
  280.                 }
  281.             }
  282.             break;
  283.         case kAEPrintDocuments:
  284.                 // We're not printing anything.
  285.             break;
  286.         case kAEQuitApplication:
  287.                 // All done.
  288.             ourApp->ExitLoop();
  289.             break;
  290.         case kAEAnswer:
  291.                 // We're not expecting any replies.
  292.             break;
  293.         default:
  294.             break;
  295.     }
  296.     return err;
  297.     
  298. }
  299.  
  300.         // Segment
  301.  
  302. #pragma segment MoG1
  303.  
  304. // =========================================================================
  305. // TApp
  306. // --------------------------------------------------------------------------
  307. // TApp::AdjustMenus
  308. //
  309. void
  310. TApp::AdjustMenus()            // override
  311. {
  312.     MenuHandle    hmFile=GetMHandle(mFile);
  313.     MenuHandle    hmEdit=GetMHandle(mEdit);
  314.             // Always avaliable.
  315.     EnableItem(hmFile, iGetInfo);
  316.     EnableItem(hmFile, iQuit);
  317.     EnableItem(hmEdit, iShowClipboard);
  318.             // Set default case.
  319.     DisableItem(hmFile, iOpenFile);
  320.     DisableItem(hmFile, iCloseFile);
  321.             // If Apple Guide is installed,
  322.             // then we can open a guide database.
  323.     if(gAGuideAvailable)
  324.     {
  325.             // Select and open guide database.
  326.         EnableItem(hmFile, iOpenFile);
  327.             // Check to see if our database is open.
  328.             // If it isn't, then set our refNum to nil.
  329.             // If we track the database in our idle loop,
  330.             // we run the risk of a race condition.
  331.             // For example, MoGuide opens a database and
  332.             // the idle checks to see if it's open.
  333.             // Apple Guide opening may not completed opening
  334.             // the database by the time MoGuide's idle loop is executed.
  335.             // So we check it here, right before we need it.
  336.         if(!AGIsDatabaseOpen(this->fGuideRefNum))
  337.             this->fGuideRefNum = nil;
  338.             // If we have a non-nil this->fGuideRefNum,
  339.             // then our database is open. Permit closing.
  340.         if(this->fGuideRefNum)
  341.             EnableItem(hmFile, iCloseFile);
  342.     }
  343. }
  344.  
  345. // ------------------------------------------------------------------------
  346. // TApp::CloseDoc
  347. //
  348. // Close the document by a "GoAway" on its document.
  349. //
  350. void
  351. TApp::CloseDoc(TDoc* docToClose)
  352. {
  353.     if(docToClose)
  354.     {
  355.             // Update current document and window pointer
  356.             // to that which is to be closed.
  357.         this->fCurDoc = docToClose;
  358.         this->fWhichWindow = this->fCurDoc->GetDocWindow();
  359.         SetPort(this->fWhichWindow);
  360.             // Let the "GoAway" handle it.
  361.         this->DoGoAway();
  362.     }
  363. }
  364.  
  365. // ------------------------------------------------------------------------
  366. // TApp::CopyToClipboard
  367. //
  368. void
  369. TApp::CopyToClipboard()
  370. {
  371.                 // There is no trap equivalent for this operation.
  372. }
  373.  
  374. // ---------------------------------------------------------------------
  375. // TApp::DoAbout
  376. // Display the "About…" box.
  377. void
  378. TApp::DoAbout()
  379. {
  380.     DialogPtr    pDlog;
  381.     Str255        versString;
  382.     short        itemType;
  383.     Handle        hItem;
  384.     Rect        box;
  385.     short        itemHit;
  386.     GrafPtr        savePort;
  387.             // Get dialog.
  388.     pDlog = GetNewDialog(rAboutDlog, kDefaultStorage, (WindowPtr) kInFrontOfAll);
  389.             // Set version informaton.
  390.     GetDItem(pDlog, iAboutTitle, &itemType, &hItem, &box);
  391.     this->GetVersion(versString);
  392.     SetIText(hItem, versString);
  393.             // Add default outline around OK button.
  394.     ShowWindow(pDlog);
  395.     GetDItem(pDlog, iAboutOk, &itemType, &hItem, &box);
  396.     GetPort(&savePort);
  397.     SetPort(pDlog);
  398.     PenSize(3,3);
  399.     InsetRect(&box, -4, -4);
  400.     FrameRoundRect(&box, 16, 16);
  401.     PenNormal();
  402.     SetPort(savePort);
  403.             // Wait for user action.
  404.     ModalDialog(kNoFilterProc, &itemHit);
  405.             // Clean up.
  406.     DisposDialog(pDlog);
  407.     HiliteMenu(0);
  408. }
  409.  
  410. // ------------------------------------------------------------------------
  411. // TApp::DoDBInfoDialog
  412. void
  413. TApp::DoDBInfoDialog(FSSpec& guideFileSpec)
  414. {
  415.     DialogPtr    pDlog;
  416.     GrafPtr        pOldPort;
  417.     Boolean        isShowing=true;
  418.  
  419.     GetPort(&pOldPort);
  420.     pDlog = GetNewDialog(kDBInfoDialogID, nil, FRONT_WINDOW);
  421.     if(pDlog)
  422.     {
  423.         Handle    hDItem;
  424.         short    itemType;
  425.         Rect    itemRect;
  426.         Str255    textStr;
  427.         OSErr    err;
  428.             // Database type
  429.         AGFileDBType databaseType;
  430.         err = AGFileGetDBType(&guideFileSpec, &databaseType);
  431.         NumToString((long) databaseType, textStr);
  432.         GetDItem(pDlog, kDBInfoType, &itemType, &hDItem, &itemRect);
  433.         SetIText(hDItem, textStr);
  434.             // Menu item name
  435.         err = AGFileGetDBMenuName(&guideFileSpec, textStr);
  436.         if(err==noErr)
  437.         {
  438.             GetDItem(pDlog, kDBInfoMenu, &itemType, &hDItem, &itemRect);
  439.             SetIText(hDItem, textStr);
  440.         }
  441.             // Selector count
  442.         short selectorCnt = AGFileGetSelectorCount(&guideFileSpec);
  443.         NumToString((long) selectorCnt, textStr);
  444.         GetDItem(pDlog, kDBInfoSelectorCnt, &itemType, &hDItem, &itemRect);
  445.         SetIText(hDItem, textStr);
  446.         AGFileSelectorType selector;
  447.         AGFileSelectorValueType value;
  448.             // Selector #1
  449.         selector = 0;
  450.         err = AGFileGetSelector(&guideFileSpec, 1, &selector, &value);
  451.         if(err==noErr && selector)
  452.         {
  453.             NumToString(value, textStr);
  454.             GetDItem(pDlog, kDBInfoSelector1Value, &itemType, &hDItem, &itemRect);
  455.             SetIText(hDItem, textStr);
  456.             textStr[0] = 4;
  457.             BlockMove(&selector, &textStr[1], 4);
  458.         }
  459.         else
  460.         {
  461.             PLstrcpy(textStr, "\p(none)");
  462.         }
  463.         GetDItem(pDlog, kDBInfoSelector1, &itemType, &hDItem, &itemRect);
  464.         SetIText(hDItem, textStr);
  465.             // Selector #2
  466.         selector = 0;
  467.         err = AGFileGetSelector(&guideFileSpec, 2, &selector, &value);
  468.         if(err==noErr && selector)
  469.         {
  470.             NumToString(value, textStr);
  471.             GetDItem(pDlog, kDBInfoSelector2Value, &itemType, &hDItem, &itemRect);
  472.             SetIText(hDItem, textStr);
  473.             textStr[0] = 4;
  474.             BlockMove(&selector, &textStr[1], 4);
  475.         }
  476.         else
  477.         {
  478.             PLstrcpy(textStr, "\p(none)");
  479.         }
  480.         GetDItem(pDlog, kDBInfoSelector2, &itemType, &hDItem, &itemRect);
  481.         SetIText(hDItem, textStr);
  482.             // Selector #3
  483.         selector = 0;
  484.         err = AGFileGetSelector(&guideFileSpec, 3, &selector, &value);
  485.         if(err==noErr && selector)
  486.         {
  487.             NumToString(value, textStr);
  488.             GetDItem(pDlog, kDBInfoSelector3Value, &itemType, &hDItem, &itemRect);
  489.             SetIText(hDItem, textStr);
  490.             textStr[0] = 4;
  491.             BlockMove(&selector, &textStr[1], 4);
  492.         }
  493.         else
  494.         {
  495.             PLstrcpy(textStr, "\p(none)");
  496.         }
  497.         GetDItem(pDlog, kDBInfoSelector3, &itemType, &hDItem, &itemRect);
  498.         SetIText(hDItem, textStr);
  499.             // Mixin?
  500.         Boolean isMixin = AGFileIsMixin(&guideFileSpec);
  501.         GetDItem(pDlog, kDBInfoMixin, &itemType, &hDItem, &itemRect);
  502.         if(isMixin)
  503.             SetIText(hDItem, "\pyes");
  504.         else
  505.             SetIText(hDItem, "\pno");
  506.             // Version
  507.         AGFileMajorRevType majorRev;
  508.         AGFileMinorRevType minorRev;
  509.         err = AGFileGetDBVersion(&guideFileSpec, &majorRev, &minorRev);
  510.         if(err==noErr)
  511.         {
  512.             Str255    majorRevStr;
  513.             Str255    minorRevStr;
  514.             NumToString((long)majorRev, majorRevStr);
  515.             NumToString((long)minorRev, minorRevStr);
  516.             PLstrcpy(textStr, majorRevStr);
  517.             PLstrcat(textStr, "\p.");
  518.             PLstrcat(textStr, minorRevStr);
  519.             GetDItem(pDlog, kDBInfoVersion, &itemType, &hDItem, &itemRect);
  520.             SetIText(hDItem, textStr);
  521.         }
  522.             // Script and Region
  523.         AGFileDBScriptType    script;
  524.         AGFileDBRegionType    region;
  525.         err = AGFileGetDBCountry(&guideFileSpec, &script, ®ion);
  526.         if(err==noErr)
  527.         {
  528.             NumToString((long)script, textStr);
  529.             GetDItem(pDlog, kDBInfoScript, &itemType, &hDItem, &itemRect);
  530.             SetIText(hDItem, textStr);
  531.             NumToString((long)region, textStr);
  532.             GetDItem(pDlog, kDBInfoRegion, &itemType, &hDItem, &itemRect);
  533.             SetIText(hDItem, textStr);
  534.         }
  535.             // Show dialog.
  536.         short    itemHit;
  537.         CenterWindow(pDlog);
  538.         SetPort(pDlog);
  539.         ShowWindow(pDlog);
  540.             // Draw default outline around OK button.
  541.         GetDItem(pDlog, kDBInfoOK, &itemType, &hDItem, &itemRect);
  542.         PenSize(3,3);
  543.         InsetRect(&itemRect, -4, -4);
  544.         FrameRoundRect(&itemRect, 16, 16);
  545.         PenNormal();
  546.             // Let user read.
  547.         while (isShowing) {
  548.             ModalDialog(nil, &itemHit);
  549.             isShowing = itemHit!=kDBInfoOK;
  550.             }
  551.         DisposeDialog(pDlog);
  552.     }
  553.     SetPort(pOldPort);
  554. }
  555.  
  556. // ------------------------------------------------------------------------
  557. // TApp::DoGoAway
  558. //
  559. // This is the close side of TApp::ShowArt, ShowClipboard, ShowFeedback.
  560. //
  561. void
  562. TApp::DoGoAway()
  563. {
  564.             // Clear our local document record if
  565.             // it's object is going away.
  566.     if(this->fCurDoc==this->fDocClip)
  567.     {
  568.         this->fDocClip = nil;
  569.         this->fScrap->SetDoc(nil);
  570.     }
  571.             // Inherit go-away action from TApplication
  572.     TApplication::DoGoAway();
  573. }
  574.  
  575. // ------------------------------------------------------------------------
  576. void
  577. TApp::DoHighLevelEvent()
  578. {
  579.     OSErr err = AEProcessAppleEvent(&this->fTheEvent);
  580. }
  581.  
  582. // ------------------------------------------------------------------------
  583. // TApp::DoIdle
  584. // Do our idle and deferred events.
  585. //
  586. // AutoStart keeps track of whether or not guide was ever provided.
  587. // The fQuitAfterGuide flag automatically terminates this
  588. // application when the guide database closes.
  589. //
  590. void
  591. TApp::DoIdle()
  592. {
  593.         // Check for guide run/quit.
  594.     if(this->fAutoStart)
  595.         if(this->fAutoStart->ShouldWeQuit())
  596.             this->ExitLoop();
  597.         // Update the scrap.
  598.     if(this->fScrap)
  599.         this->fScrap->DoIdle();
  600. }
  601.  
  602. // ------------------------------------------------------------------------
  603. // TApp::DoMenuCommand
  604. // This is called when an item is chosen from the menu bar (after calling
  605. // MenuSelect or MenuKey). It does the right thing for each command.
  606. void
  607. TApp::DoMenuCommand(short menuID, short menuItem)        //  override
  608. {
  609.  
  610.     Str255    daName;
  611.     short    daRefNum;
  612.     FSSpec    fileSpec;
  613.  
  614.     switch (menuID)
  615.     {
  616.         case mApple:
  617.             switch ( menuItem )
  618.             {
  619.                 case iAbout:// bring up alert for About
  620.                     this->DoAbout();
  621.                     break;
  622.                 default:    // all non-About items in this menu are DAs et al 
  623.                     GetItem(GetMHandle(mApple), menuItem, daName);
  624.                     daRefNum = OpenDeskAcc(daName);
  625.                     break;
  626.             } // switch
  627.             break;
  628.         case mFile:
  629.             switch ( menuItem )
  630.             {
  631.                 case iOpenFile:
  632.                         // User selects desired database.
  633.                         // Tell Apple Guide to open it..
  634.                     if(this->SelectFile(fileSpec)==noErr)
  635.                         AlertIfError(this->OpenGuideDatabase(&fileSpec));
  636.                     break;
  637.                 case iCloseFile:
  638.                     AlertIfError(AGClose(&this->fGuideRefNum));
  639.                     break;
  640.                 case iGetInfo:
  641.                     if(this->SelectFile(fileSpec)==noErr)
  642.                         this->DoDBInfoDialog(fileSpec);
  643.                     break;
  644.                 case iQuit:
  645.                     this->ExitLoop();
  646.                     break;
  647.                 default:
  648.                     break;
  649.             } // switch
  650.             break;
  651.         case mEdit:
  652.             switch ( menuItem )
  653.             {
  654.                 case iCopy:
  655.                     this->CopyToClipboard();
  656.                     break;
  657.                 case iShowClipboard:
  658.                     this->ShowClipboard();
  659.                     break;
  660.                 default:
  661.                     break;
  662.             } // switch
  663.             break;
  664.         default:
  665.             break;
  666.     } // switch
  667.             // Turn off menu hilite.
  668.     HiliteMenu(0);
  669. } // DoMenuCommand
  670.  
  671. //-----------------------------------------------------------------------
  672. // TApp::EventLoop
  673. // We override the TApplication::EventLoop to add processing
  674. // of high-level events.  While we're at it, since we're
  675. // System 7 only, we can always call WNE.
  676. //
  677. void
  678. TApp::EventLoop()
  679. {
  680.         Boolean        gotEvent;
  681.         EventRecord    tEvt;
  682.                 // call setup routine
  683.         this->SetUp();
  684.                 // The loop
  685.         while(!this->fDone)
  686.         {
  687.             this->SetDoc();
  688.             gotEvent = WaitNextEvent(everyEvent, &tEvt, SleepVal(), this->fMouseRgn);
  689.             this->fTheEvent = tEvt;
  690.             if(!gotEvent)
  691.                 this->DoIdle();
  692.             else        // A real event
  693.             {
  694.                 this->AdjustCursor();
  695.                 switch (fTheEvent.what)
  696.                 {
  697.                     case mouseDown:
  698.                         this->DoMouseDown();
  699.                         break;
  700.                                             
  701.                     case mouseUp:
  702.                         this->DoMouseUp();
  703.                         break;
  704.                                             
  705.                     case keyDown:
  706.                     case autoKey:
  707.                         this->DoKeyDown();
  708.                         break;
  709.                                             
  710.                     case updateEvt:
  711.                         this->DoUpdateEvt();                
  712.                         break;
  713.                                             
  714.                     case diskEvt:
  715.                         this->DoDiskEvt();
  716.                         break;
  717.                                             
  718.                     case activateEvt:
  719.                         this->DoActivateEvt();
  720.                         break;
  721.                                             
  722.                     case osEvt:
  723.                         this->DoOSEvent();
  724.                         break;
  725.                                             
  726.                     case kHighLevelEvent:
  727.                         this->DoHighLevelEvent();
  728.                         break;
  729.  
  730.                     default:
  731.                         break;
  732.                         
  733.                 } // end switch (fTheEvent.what)
  734.             }
  735.                 // update the cursor shape as needed after the event
  736.             this->AdjustCursor();
  737.         }
  738.             // call cleanup handler
  739.         this->CleanUp();
  740. }
  741.  
  742.  
  743. // ------------------------------------------------------------------------
  744. // TApp::GetVersion
  745. // Get the current version of the application.
  746. // Uses the long version string from 'vers' resource #1.
  747. //
  748. void
  749. TApp::GetVersion(Str255 versStr)
  750. {
  751.     Handle    hRes;
  752.     char    *pRes;
  753.     short    i;
  754.  
  755.     if (hRes = GetResource('vers', 1))
  756.     {
  757.         pRes = *hRes;
  758.         pRes += 7 + pRes[6];        // long version pstring
  759.         for(i=0; i<=pRes[0]; i++)
  760.             versStr[i] = pRes[i];    // copy version pstring
  761.     }
  762. }
  763.  
  764. // ---------------------------------------------------------------------
  765. // TApp::OpenGuideDatabase
  766. // Open a guide database.
  767. // Return noErr if successful.
  768. // Update TApp database variables.
  769. //
  770. OSErr
  771. TApp::OpenGuideDatabase(FSSpec *pFileSpec)
  772. {
  773.     OSErr result=noErr;
  774.     if(gAGuideAvailable)
  775.     {
  776.             // Ask Apple Guide to open database.
  777.         result = AGOpen(pFileSpec, 0, nil, &this->fGuideRefNum);
  778.     }
  779.     return result;
  780. }
  781.  
  782. // ---------------------------------------------------------------------
  783. // TApp::Init
  784. // Do the things that our derived application class requires.
  785. // Basic initialization, before we start.
  786. // We store our application object in the core event handler
  787. // refCon so that the handler can use it.  Avoids making it a global.
  788. // Return noErr if successful.
  789. OSErr
  790. TApp::Init()
  791. {
  792.     OSErr        err;
  793.     SysEnvRec    envRec;
  794.     (void) SysEnvirons(curSysEnvVers, &envRec);
  795.             // System 7 is required
  796.     if(envRec.systemVersion<0x0700)
  797.     {
  798.         this->BigBadError(kUserStrId, kStrNotSevenOh);
  799.     }
  800.             // Our menuBar to use when we Start.
  801.     this->fMenuBarID = rMenuBar;
  802.             // Also clear our variables or we may get a bus error
  803.             // at the first DoIdle (before we get the start event.)
  804.     this->fDocClip = nil;
  805.     this->fScrap = nil;
  806.     this->fAutoStart = nil;
  807.             // Install our core event handler.
  808.             // We put the TApp object in the refCon.
  809.     err = AEInstallEventHandler(kCoreEventClass,
  810.                                 typeWildCard,
  811.                                 (EventHandlerProcPtr) TApp::HandleAECore,
  812.                                 (long)this,
  813.                                 kIsNotSysHandler);
  814.             // Our custom event handler is installed in the derived class.
  815.             // Starting is done in TApp::HandleAECore.
  816.             // We wait until that happens before proceeding.
  817.     this->fGuideRefNum = nil;
  818.             // Check to see if the Apple Guide traps are available.
  819.     long result=0;
  820.     err = Gestalt(gestaltHelpMgrAttr, &result);
  821.     gAGuideAvailable = (err==noErr && (result & (1 << gestaltAppleGuidePresent)));
  822.             // Auto-Start object.
  823.     this->fAutoStart = new TAStart;
  824.     if(!this->fAutoStart)
  825.     {
  826.         return kErrNoAutoStartObj;
  827.     }
  828.             // Initialize the auto-start object.
  829.     err = this->fAutoStart->Init();
  830.     return err;
  831. }
  832.  
  833. // ------------------------------------------------------------------------
  834. // TApp::Quit
  835. // We're quitting, so delete everything.
  836. // All of this probably goes away in the application heap anyway,
  837. // we're just being compulsively tidy.
  838. //
  839. void
  840. TApp::Quit()
  841. {
  842.     SetCursor(*GetCursor(watchCursor));
  843.             // Close the guide database and make Apple Guide quit.
  844.             // Here's a case where we just do it without
  845.             // any checking to see if a database is open or
  846.             // Apple Guide is running. Nor do we check
  847.             // for errors. The API should be robust enough to
  848.             // handle all the possibilities without causing any problem.
  849.     if(gAGuideAvailable)
  850.     {
  851.         (void) AGClose(&this->fGuideRefNum);
  852.         (void) AGQuit();
  853.     }
  854.             // Remove our scrap object.
  855.     if(this->fScrap)
  856.         delete this->fScrap;
  857.             // Remove our Auto-Start object.
  858.     if(this->fAutoStart)
  859.         delete this->fAutoStart;
  860.             // Remove core event handler.
  861.     (void) AERemoveEventHandler(kCoreEventClass,
  862.                                 typeWildCard,
  863.                                 kHandlerNotRequired,
  864.                                 kIsNotSysHandler);
  865.     SetCursor(&qd.arrow);
  866. }
  867.  
  868. // ------------------------------------------------------------------------
  869. // TApp::SendEventToSelf
  870. // Our mechanism for full-factoring.
  871. //
  872. OSErr
  873. TApp::SendEventToSelf(AEEventID theEvent)
  874. {
  875.     AppleEvent    theMessage;
  876.     AppleEvent    theReply;
  877.     OSErr        err=noErr;
  878.             // Make address to self.
  879.     AEAddressDesc        addrDesc;
  880.     ProcessSerialNumber    psn;
  881.     psn.highLongOfPSN = 0;
  882.     psn.lowLongOfPSN = kCurrentProcess;
  883.     err = AECreateDesc(typeProcessSerialNumber, (Ptr) &psn,
  884.                                           sizeof(psn), &addrDesc);
  885.     if(err==noErr)
  886.     {
  887.             // Create AppleEvent for theMessage
  888.         err = AECreateAppleEvent(kAEClassCustom,
  889.                                 theEvent,
  890.                                 &addrDesc, kAutoGenerateReturnID,
  891.                                 kAnyTransactionID, &theMessage);
  892.         if(err==noErr)
  893.         {
  894.                 // Send message.
  895.             err = AESend(&theMessage, &theReply,
  896.                             kAENoReply, kAEHighPriority,
  897.                             kNoTimeOut, nil, nil);
  898.                 // Clean-up
  899.             (void) AEDisposeDesc(&theMessage);
  900.             (void) AEDisposeDesc(&theReply);
  901.         }
  902.         (void) AEDisposeDesc(&addrDesc);
  903.     }
  904.     return err;
  905. }
  906.  
  907. // ------------------------------------------------------------------------
  908. // TApp::SelectFile
  909. // Ask user for database file.
  910. // Dialog ID=rSelectFileDlog (OpenDLOG.rsrc)
  911. // contains a friendly prompt text line.
  912. // Return noErr if successful.
  913. //
  914. OSErr
  915. TApp::SelectFile(FSSpec& selectedFile)
  916. {
  917.     OSErr err=noErr;
  918.     Point where = {-1,-1};
  919.     StandardFileReply    reply;
  920.         // Display both main and mixin files.
  921.     short numTypes = 2;
  922.     SFTypeList typeList = {kAGFileMain, kAGFileMixin, 0, 0};
  923.             // Use custom dialog if available, otherwise use standard.
  924.     Handle hDlog = GetResource('DLOG', rSelectFileDlog);
  925.     if(hDlog!=nil && ResError()==noErr)
  926.     {
  927.         CustomGetFile(nil, numTypes, typeList, &reply,
  928.                         rSelectFileDlog, where,
  929.                         nil, nil, nil, nil, nil);
  930.     }
  931.     else
  932.     {
  933.         StandardGetFile(nil, numTypes, typeList, &reply);
  934.     }
  935.     if(reply.sfGood)
  936.     {
  937.         selectedFile = reply.sfFile;
  938.     }
  939.     else
  940.     {
  941.         selectedFile.name[0] = 0;
  942.         err = kSelectFileCancel;
  943.     }
  944.     return err;
  945. }
  946.  
  947. // ------------------------------------------------------------------------
  948. // TApp::SetDoc
  949. // Set the current window and document.
  950. //
  951. void
  952. TApp::SetDoc()
  953. {
  954.     this->fWhichWindow = FrontWindow();
  955.     if(this->fWhichWindow==nil)
  956.     {
  957.             // No window, default to the last window on the list.
  958.         //this->fWhichWindow = *(WindowPtr*)WindowList;
  959.         this->fCurDoc = nil;
  960.     }
  961.     else
  962.     {
  963.         this->fCurDoc = this->fDocList->FindDoc(this->fWhichWindow);
  964.         SetPort(this->fWhichWindow);
  965.     }
  966. }
  967.  
  968. // ------------------------------------------------------------------------
  969. // TApp::ShowClipboard
  970. // Opens the clipboard window.
  971. // The document window is closed by the go-away box (TApp::DoGoAway).
  972. //
  973. void
  974. TApp::ShowClipboard()
  975. {
  976.     if(this->fDocClip==nil)
  977.     {
  978.             // Clipboard window is not present.
  979.             // Create a document and window for the clipboard.
  980.         this->fDocClip = new TDocClip(kClipboardWinResID);
  981.         if(this->fDocClip)
  982.         {
  983.             this->fDocList->AddDoc((TDocument*) this->fDocClip);
  984.                 // Set the clipboard document window's collaborator.
  985.             this->fDocClip->SetScrapObj(this->fScrap);
  986.             this->fDocClip->SetApp(this);
  987.                 // Set the document collaborator for the scrap object.
  988.             this->fScrap->SetDoc(this->fDocClip);
  989.                 // Update current document and window pointer.
  990.             this->fCurDoc = this->fDocClip;
  991.             this->fWhichWindow = this->fCurDoc->GetDocWindow();
  992.             SetPort(this->fWhichWindow);
  993.         }
  994.     }
  995.         // Better show it, or at least bring it to the front.
  996.     this->fDocClip->Show();
  997.             // Invalidate window so that it will be updated and drawn.
  998.     this->fDocClip->Invalidate();
  999. }
  1000.  
  1001. // ---------------------------------------------------------------------
  1002. // TApp::Start
  1003. // Startup the application.  Comes after the Init.
  1004. // The Init is done in main.  The Start comes with the oapp or odoc event.
  1005. // Return noErr if successful.
  1006. OSErr
  1007. TApp::Start()
  1008. {
  1009.     OSErr    err=noErr;
  1010.     SetCursor(*GetCursor(watchCursor));
  1011.             // Install menus.
  1012.     Handle menuBar = GetNewMBar(this->fMenuBarID);
  1013.     if(!menuBar)
  1014.     {
  1015.         return kErrNoMenuBar;
  1016.     }
  1017.     SetMenuBar(menuBar);        // Copy to current menu list.
  1018.     DisposHandle(menuBar);        // Don't need it anymore.
  1019.             // Add DA names to Apple menu.
  1020.     AddResMenu(GetMHandle(mApple), 'DRVR');
  1021.     DrawMenuBar();
  1022.             // Scrap object
  1023.     this->fScrap = new TScrap;
  1024.     if(!this->fScrap)
  1025.     {
  1026.         return kErrNoScrapObject;
  1027.     }
  1028.     SetCursor(&qd.arrow);
  1029.     return err;
  1030. }
  1031.  
  1032. // =========================================================================
  1033. // TAStart
  1034. // ------------------------------------------------------------------------
  1035. TAStart::TAStart()
  1036. {
  1037.         // Guide collaborator.
  1038.     this->fGuideRefNum = nil;
  1039.         // Clear our flags.
  1040.     this->fGuideHasRun = false;
  1041.     this->fQuitAfterGuide = false;
  1042.         // The preset guide database file
  1043.     this->fPresetGuideFile.vRefNum = 0;
  1044.     this->fPresetGuideFile.parID = 0;
  1045.     this->fPresetGuideFile.name[0] = 0;
  1046. }
  1047.  
  1048. // ---------------------------------------------------------------------
  1049. // TAStart::AttemptAutoStart
  1050. // Start a guide database per the autoStart resource.
  1051. // Return an error code if a self auto-start fails.
  1052. // In all other cases, return noErr.
  1053. // An alert is also given if a self auto-start fails.
  1054. //
  1055. // Given that enough startup information is provided,
  1056. // the "autoStartFlag" flag determines whether or not the
  1057. // auto-start is actually done.
  1058. //
  1059. // The following is always done, auto-start or not:
  1060. //    • The guide file spec (fPresetGuideFile) specified in the autoStart
  1061. //      resource will be saved for future use.
  1062. //    • The fQuitAfterGuide flag will be set to the resource field value.
  1063. //
  1064. AGErr
  1065. TAStart::AttemptAutoStart()
  1066. {
  1067.     AGErr result=noErr;
  1068.     Boolean    okayToStart=false;
  1069.             // Default to this application's folder.
  1070.     this->fPresetGuideFile.vRefNum = -*(short*)SFSaveDisk;
  1071.     this->fPresetGuideFile.parID = *(long*)CurDirStore;
  1072.     this->fPresetGuideFile.name[0] = 0;
  1073.             // AutoStart resource?
  1074.     Handle hSpecRes = GetIndResource(kResAutoStart, 1);
  1075.     if(hSpecRes)
  1076.     {
  1077.             // We have an autoStart resource, get contents.
  1078.         HLock(hSpecRes);
  1079.         StartSpecPtr pStartSpec = (StartSpecPtr) *hSpecRes;
  1080.             // Set the quit-after-guide flag.
  1081.         this->fQuitAfterGuide = pStartSpec->quitAfterGuide;
  1082.             // Check for auto-start of self.
  1083.         if(pStartSpec->autoStartFlag==kAutoSelf)
  1084.         {
  1085.                 // Auto-start self, get file spec for self.
  1086.             ProcessSerialNumber psn;
  1087.             OSErr err = GetCurrentProcess(&psn);
  1088.             if(err==noErr)
  1089.             {
  1090.                 FSSpec            appFile;
  1091.                 ProcessInfoRec    processInfo;
  1092.                 processInfo.processInfoLength = sizeof(ProcessInfoRec);
  1093.                 processInfo.processName = nil;
  1094.                 processInfo.processAppSpec = &appFile;
  1095.                 err = GetProcessInformation(&psn, &processInfo);
  1096.                 if(err==noErr)
  1097.                 {
  1098.                     this->fPresetGuideFile = appFile;
  1099.                     okayToStart = pStartSpec->autoStartFlag;
  1100.                 }
  1101.             }
  1102.         }
  1103.         else
  1104.         {
  1105.                 // Not a auto-start of self, get file name from resource.
  1106.             if(pStartSpec->fileName[0]>0)
  1107.             {
  1108.                     // We have a file name, that has first priority.
  1109.                 PLstrcpy(this->fPresetGuideFile.name, pStartSpec->fileName);
  1110.                     // If autoStartFlag flag is set, do auto-start.
  1111.                 okayToStart = pStartSpec->autoStartFlag;
  1112.             }
  1113.             else if(pStartSpec->type>0)
  1114.             {
  1115.                     // Else, we have a guide file type, find a file of that type.
  1116.                 {
  1117.                     short    vRefNum = (-*(short*)SFSaveDisk);
  1118.                     long    dirID = (*(long*)CurDirStore);
  1119.                     Boolean    wantMixin = false;
  1120.                     short    dbIndex = 1;
  1121.                     if(AGFileGetIndDB(vRefNum, dirID,
  1122.                                         pStartSpec->type, wantMixin,
  1123.                                         dbIndex, &this->fPresetGuideFile)==noErr)
  1124.                     {
  1125.                             // Found a file of that type, start it.
  1126.                             // If autoStartFlag flag is set, do auto-start.
  1127.                         okayToStart = pStartSpec->autoStartFlag;
  1128.                     }
  1129.                 }
  1130.             }
  1131.         }
  1132.         HUnlock(hSpecRes);
  1133.             // We've setup fPresetGuideFile, let's see if it's okay to start.
  1134.             // No autostart if option key is down.
  1135.         union
  1136.         {
  1137.             KeyMap asMap;
  1138.             Byte asBytes[16];
  1139.         };
  1140.         GetKeys(asMap);
  1141.         Boolean optionKeyNotDown = !(asBytes[0x3A>>3]&(1<<(0x3A&0x07))?true:false);
  1142.         if(okayToStart && optionKeyNotDown)
  1143.         {
  1144.             if(!gAGuideAvailable)
  1145.                 Alert(rAutoStartNeedAlrt, nil);    // Need Apple Guide
  1146.             else
  1147.             {
  1148.                     // Apple Guide is available and we want an auto-start.
  1149.                     // Use topic ID if present, otherwise do general guide startup.
  1150.                 if(pStartSpec->sequenceID)
  1151.                     result = AGOpenWithSequence(&this->fPresetGuideFile,
  1152.                                                  0, nil,
  1153.                                                 pStartSpec->sequenceID,
  1154.                                                 &this->fGuideRefNum);
  1155.                 else
  1156.                     result = AGOpen(&this->fPresetGuideFile,
  1157.                                         0, nil, 
  1158.                                         &this->fGuideRefNum);
  1159.                     // If a self auto-start failed, then we can't go any further.
  1160.                     // Put up an alert.
  1161.                 if(okayToStart==kAutoSelf)
  1162.                 {
  1163.                     if(result!=noErr || AGGetStatus()!=kAGIsActive)
  1164.                     {
  1165.                             // Give the all-purpose "beats me" auto-start alert.
  1166.                             Alert(rAutoStartUnknownAlrt, nil);
  1167.                     }
  1168.                 } // if(okayToStart…
  1169.             } // else if(optionKeyNotDown…
  1170.         } // if(okayToStart…
  1171.     }
  1172.     return result;
  1173. }
  1174.  
  1175. // ------------------------------------------------------------------------
  1176. // TAStart::Init
  1177. // Initialize the TAStart object.
  1178. // Return noErr if successful.
  1179. //
  1180. OSErr
  1181. TAStart::Init()
  1182. {
  1183.     return noErr;
  1184. }
  1185.  
  1186. // ------------------------------------------------------------------------
  1187. // TAStart::ShouldWeQuit
  1188. // Return true if we should quit.
  1189. // We quit if guide has run
  1190. // AND it isn't running now
  1191. // AND fQuitAfterGuide is true.
  1192. //
  1193. Boolean
  1194. TAStart::ShouldWeQuit()
  1195. {
  1196.     Boolean result=false;
  1197.         // Check for guide run/quit.
  1198.     if(gAGuideAvailable)
  1199.     {
  1200.         if(AGGetStatus()==kAGIsSleeping || AGGetStatus()==kAGIsActive)
  1201.             this->fGuideHasRun = true;
  1202.         else
  1203.             result = (this->fGuideHasRun && this->fQuitAfterGuide);
  1204.     }
  1205.     return result;
  1206. }
  1207.  
  1208. // =========================================================================
  1209. // TScrap
  1210. // ------------------------------------------------------------------------
  1211. TScrap::TScrap()
  1212. {
  1213.     this->fLastScrapCount = 0;
  1214.         // Initialize our value for the scrap count.
  1215.     (void) this->Update();
  1216. }
  1217.  
  1218. // ------------------------------------------------------------------------
  1219. // TScrap::DoIdle
  1220. // Do any action required during the idle processing.
  1221. //
  1222. void
  1223. TScrap::DoIdle()
  1224. {
  1225.         // If the scrap is updated and the clipboard window
  1226.         // is showing, update/invalidate the clipboard window.
  1227.     if(this->Update())
  1228.         if(this->fDocClip)
  1229.             this->fDocClip->Invalidate();
  1230. }
  1231.  
  1232. // ------------------------------------------------------------------------
  1233. // TScrap::Draw
  1234. void
  1235. TScrap::Draw(WindowPtr pWin)
  1236. {
  1237.     SetPort(pWin);
  1238.     TextFont(monaco);
  1239.     TextSize(9);
  1240.     long offset;
  1241.     long scrapLen = GetScrap(nil, 'TEXT', &offset);
  1242.     if(scrapLen>0)
  1243.     {
  1244.         Handle hContent = NewHandle(scrapLen);
  1245.         if(hContent)
  1246.         {
  1247.             scrapLen = GetScrap(hContent, 'TEXT', &offset);
  1248.             HLock(hContent);
  1249.             TextBox(*hContent, scrapLen, &pWin->portRect, teFlushDefault);
  1250.             DisposeHandle(hContent);
  1251.         }
  1252.     }
  1253. }
  1254.  
  1255. // ------------------------------------------------------------------------
  1256. // TScrap::Put
  1257. // Put the contents of the handle into the scrap.
  1258. // The handle is not disposed.
  1259. //
  1260. void
  1261. TScrap::Put(Handle hToScrap)
  1262. {
  1263.     if(hToScrap)
  1264.     {
  1265.         HLock(hToScrap);
  1266.         (void) ZeroScrap();
  1267.         (void) PutScrap(GetHandleSize(hToScrap), 'TEXT', *hToScrap);
  1268.         HUnlock(hToScrap);
  1269.     }
  1270. }
  1271.  
  1272. // ------------------------------------------------------------------------
  1273. // TScrap::Update
  1274. // Set the value of the last scrapCount.
  1275. // Return true if it has changed.
  1276. Boolean
  1277. TScrap::Update()
  1278. {
  1279.     Boolean hasChanged=false;
  1280.     PScrapStuff pScrapStuff = InfoScrap();
  1281.     if(pScrapStuff)
  1282.     {
  1283.         hasChanged = (pScrapStuff->scrapCount!=this->fLastScrapCount);
  1284.         this->fLastScrapCount = pScrapStuff->scrapCount;
  1285.     }
  1286.     return hasChanged;
  1287. }
  1288.  
  1289.